#include "GLHeaders.hpp"
#include "Texture.hpp"
#include "Color.hpp"
#include "Interpolation.hpp"
#include "Perlin.hpp"
#include "Vector.hpp"
#include <cmath>

//////////////////////
//    Constructor   //
//////////////////////

Texture::Texture(void){
  width = 0;
  height = 0;
  data = NULL;
}

Texture::Texture(
    const GLsizei lengthX,
    const GLsizei lengthY,
    const Color &color){
  if(lengthX >= 0 && lengthY >=0){
    width = lengthX;
    height = lengthY;
    data = new Color[width*height];
    for(int y=0; y < height; y++){
      for(int x=0; x < width; x++){
        this->data[y*width+x] = color;
      }
    }
  }
  else{ // Looser !!
    width = 0;
    height = 0;
    data = NULL;
  }
}

Texture::Texture(
    const GLsizei lengthX,
    const GLsizei lengthY,
    const GLfloat fr,
    const GLfloat fg,
    const GLfloat fb,
    const GLfloat fa){
  if(lengthX >= 0 && lengthY >=0){
    width = lengthX;
    height = lengthY;
    data = new Color[width*height];
     for(int y=0; y < height; y++){
      for(int x=0; x < width; x++){
        this->data[y*width+x] = Color::Color(fr, fg, fb, fa);
      }
    }
  }
  else{ // Looser !!
    width = 0;
    height = 0;
    data = NULL;
  }
}

Texture::Texture(
    const GLsizei lengthX,
    const GLsizei lengthY,
    const GLint br,
    const GLint bg,
    const GLint bb,
    const GLint ba){
  if(lengthX >= 0 && lengthY >=0){
    width = lengthX;
    height = lengthY;
    data = new Color[width*height];
     for(int y=0; y < height; y++){
      for(int x=0; x < width; x++){
        this->data[y*width+x] = Color::Color(br, bg, bb, ba);
      }
    }
  }
  else{ // Looser !!
    width = 0;
    height = 0;
    data = NULL;
  }
}

Texture::Texture(
    const GLsizei lengthX,
    const GLsizei lengthY,
    const GLfloat value){
  if(lengthX >= 0 && lengthY >=0){
    width = lengthX;
    height = lengthY;
    data = new Color[width*height];
     for(int y=0; y < height; y++){
      for(int x=0; x < width; x++){
        this->data[y*width+x] = value;
      }
    }
  }
  else{ // Looser !!
    width = 0;
    height = 0;
    data = NULL;
  }
}

Texture::Texture(
    const GLsizei lengthX,
    const GLsizei lengthY,
    const GLint value){
  if(lengthX >= 0 && lengthY >=0){
    width = lengthX;
    height = lengthY;
    data = new Color[width*height];
      for(int y=0; y < height; y++){
      for(int x=0; x < width; x++){
        this->data[y*width+x] = value;
      }
    }
  }
  else{ // Looser !!
    width = 0;
    height = 0;
    data = NULL;
  }
}

Texture::Texture(const Texture& texture){
  width = texture.width;
  height = texture.height;
  data = new Color[width*height];
  for(int y=0; y < height; y++)
    for(int x=0; x < width; x++)
      this->data[y*width+x] = texture.data[y*width+x];
}

//////////////////////
//    Destructor    //
//////////////////////

  Texture::~Texture(void){
    if(NULL != data)
      delete[] data;
  }

////////////////////////////////
//    Arithmetic operations   //
////////////////////////////////

Texture Texture::operator + () const{
  Texture tmp;
  tmp.width = width;
  tmp.height = height;
  tmp.data = new Color[width*height];
  for(int y=0; y < height; y++)
    for(int x=0; x < width; x++)
      tmp.data[y*width+x] = data[y*width+x];
  return tmp;
}

Texture Texture::operator - () const{
  Texture tmp;
  tmp.width = width;
  tmp.height = height;
  tmp.data = new Color[width*height];
  for(int y=0; y < height; y++)
    for(int x=0; x < width; x++)
      tmp.data[y*width+x] = -data[y*width+x];
  return tmp;
}

Texture Texture::operator + (const Texture &rhs) const {
  Texture tmp;
  tmp.width = width;
  tmp.height = height;
  tmp.data = new Color[width*height];
  for(int y=0; y < height; y++)
    for(int x=0; x < width; x++)
      tmp.data[y*width+x] = data[y*width+x] + rhs.data[y*width+x];
  return tmp;
}

Texture Texture::operator + (const Color &value) const {
  Texture tmp;
  tmp.width = width;
  tmp.height = height;
  tmp.data = new Color[width*height];
  for(int y=0; y < height; y++)
    for(int x=0; x < width; x++)
      tmp.data[y*width+x] = data[y*width+x] + value;
  return tmp;
}

Texture Texture::operator + (const GLfloat value) const {
  Texture tmp;
  tmp.width = width;
  tmp.height = height;
  tmp.data = new Color[width*height];
  for(int y=0; y < height; y++)
    for(int x=0; x < width; x++)
      tmp.data[y*width+x] = data[y*width+x] + value;
  return tmp;
}

Texture Texture::operator + (const GLint value) const {
  Texture tmp;
  tmp.width = width;
  tmp.height = height;
  tmp.data = new Color[width*height];
  for(int y=0; y < height; y++)
    for(int x=0; x < width; x++)
      tmp.data[y*width+x] = data[y*width+x] + value;
  return tmp;
}

Texture Texture::operator - (const Texture &rhs) const {
  Texture tmp;
  tmp.width = width;
  tmp.height = height;
  tmp.data = new Color[width*height];
  for(int y=0; y < height; y++)
    for(int x=0; x < width; x++)
      tmp.data[y*width+x] = data[y*width+x] - rhs.data[y*width+x];
  return tmp;
}

Texture Texture::operator - (const Color &value) const {
  Texture tmp;
  tmp.width = width;
  tmp.height = height;
  tmp.data = new Color[width*height];
  for(int y=0; y < height; y++)
    for(int x=0; x < width; x++)
      tmp.data[y*width+x] = data[y*width+x] - value;
  return tmp;
}

Texture Texture::operator - (const GLfloat value) const {
  Texture tmp;
  tmp.width = width;
  tmp.height = height;
  tmp.data = new Color[width*height];
  for(int y=0; y < height; y++)
    for(int x=0; x < width; x++)
      tmp.data[y*width+x] = data[y*width+x] - value;
  return tmp;
}

Texture Texture::operator - (const GLint value) const {
  Texture tmp;
  tmp.width = width;
  tmp.height = height;
  tmp.data = new Color[width*height];
  for(int y=0; y < height; y++)
    for(int x=0; x < width; x++)
      tmp.data[y*width+x] = data[y*width+x] - value;
  return tmp;
}

Texture Texture::operator * (const Texture &rhs) const {
  Texture tmp;
  tmp.width = width;
  tmp.height = height;
  tmp.data = new Color[width*height];
  for(int y=0; y < height; y++)
    for(int x=0; x < width; x++)
      tmp.data[y*width+x] = data[y*width+x] * rhs.data[y*width+x];
  return tmp;
}

Texture Texture::operator * (const Color &value) const {
  Texture tmp;
  tmp.width = width;
  tmp.height = height;
  tmp.data = new Color[width*height];
  for(int y=0; y < height; y++)
    for(int x=0; x < width; x++)
      tmp.data[y*width+x] = data[y*width+x] * value;
  return tmp;
}

Texture Texture::operator * (const GLfloat value) const {
  Texture tmp;
  tmp.width = width;
  tmp.height = height;
  tmp.data = new Color[width*height];
  for(int y=0; y < height; y++)
    for(int x=0; x < width; x++)
      tmp.data[y*width+x] = data[y*width+x] * value;
  return tmp;
}

Texture Texture::operator / (const Texture &rhs) const {
  Texture tmp;
  tmp.width = width;
  tmp.height = height;
  tmp.data = new Color[width*height];
  for(int y=0; y < height; y++)
    for(int x=0; x < width; x++)
      tmp.data[y*width+x] = data[y*width+x] / rhs.data[y*width+x];
  return tmp;
}

Texture Texture::operator / (const Color &value) const {
  Texture tmp;
  tmp.width = width;
  tmp.height = height;
  tmp.data = new Color[width*height];
  for(int y=0; y < height; y++)
    for(int x=0; x < width; x++)
      tmp.data[y*width+x] = data[y*width+x] / value;
  return tmp;
}

Texture Texture::operator / (const GLfloat value) const {
  Texture tmp;
  tmp.width = width;
  tmp.height = height;
  tmp.data = new Color[width*height];
  for(int y=0; y < height; y++)
    for(int x=0; x < width; x++)
      tmp.data[y*width+x] = data[y*width+x] / value;
  return tmp;
}

//////////////////////////
//    Friend Methods    //
//////////////////////////

Texture operator + (const Color &value, const Texture &rhs){
  Texture tmp;
  tmp.width = rhs.width;
  tmp.height = rhs.height;
  tmp.data = new Color[tmp.width*tmp.height];
  for(int y=0; y < tmp.height; y++)
    for(int x=0; x < tmp.width; x++)
      tmp.data[y*tmp.width+x] = value + rhs.data[y*tmp.width+x];
  return tmp;
}

Texture operator + (const GLfloat value, const Texture &rhs){
  Texture tmp;
  tmp.width = rhs.width;
  tmp.height = rhs.height;
  tmp.data = new Color[tmp.width*tmp.height];
  for(int y=0; y < tmp.height; y++)
    for(int x=0; x < tmp.width; x++)
      tmp.data[y*tmp.width+x] = value + rhs.data[y*tmp.width+x];
  return tmp;
}

Texture operator + (const GLint value, const Texture &rhs){
  Texture tmp;
  tmp.width = rhs.width;
  tmp.height = rhs.height;
  tmp.data = new Color[tmp.width*tmp.height];
  for(int y=0; y < tmp.height; y++)
    for(int x=0; x < tmp.width; x++)
      tmp.data[y*tmp.width+x] = value + rhs.data[y*tmp.width+x];
  return tmp;
}

Texture operator - (const Color &value, const Texture &rhs){
  Texture tmp;
  tmp.width = rhs.width;
  tmp.height = rhs.height;
  tmp.data = new Color[tmp.width*tmp.height];
  for(int y=0; y < tmp.height; y++)
    for(int x=0; x < tmp.width; x++)
      tmp.data[y*tmp.width+x] = value - rhs.data[y*tmp.width+x];
  return tmp;
}

Texture operator - (const GLfloat value, const Texture &rhs){
  Texture tmp;
  tmp.width = rhs.width;
  tmp.height = rhs.height;
  tmp.data = new Color[tmp.width*tmp.height];
  for(int y=0; y < tmp.height; y++)
    for(int x=0; x < tmp.width; x++)
      tmp.data[y*tmp.width+x] = value - rhs.data[y*tmp.width+x];
  return tmp;
}

Texture operator - (const GLint value, const Texture &rhs){
  Texture tmp;
  tmp.width = rhs.width;
  tmp.height = rhs.height;
  tmp.data = new Color[tmp.width*tmp.height];
  for(int y=0; y < tmp.height; y++)
    for(int x=0; x < tmp.width; x++)
      tmp.data[y*tmp.width+x] = value - rhs.data[y*tmp.width+x];
  return tmp;
}

Texture operator * (const Color &value, const Texture &rhs){
  Texture tmp;
  tmp.width = rhs.width;
  tmp.height = rhs.height;
  tmp.data = new Color[tmp.width*tmp.height];
  for(int y=0; y < tmp.height; y++)
    for(int x=0; x < tmp.width; x++)
      tmp.data[y*tmp.width+x] = value * rhs.data[y*tmp.width+x];
  return tmp;
}

Texture operator * (const GLfloat value, const Texture &rhs){
  Texture tmp;
  tmp.width = rhs.width;
  tmp.height = rhs.height;
  tmp.data = new Color[tmp.width*tmp.height];
  for(int y=0; y < tmp.height; y++)
    for(int x=0; x < tmp.width; x++)
      tmp.data[y*tmp.width+x] = value * rhs.data[y*tmp.width+x];
  return tmp;
}

Texture operator / (const Color &value, const Texture &rhs){
  Texture tmp;
  tmp.width = rhs.width;
  tmp.height = rhs.height;
  tmp.data = new Color[tmp.width*tmp.height];
  for(int y=0; y < tmp.height; y++)
    for(int x=0; x < tmp.width; x++)
      tmp.data[y*tmp.width+x] = value / rhs.data[y*tmp.width+x];
  return tmp;
}

Texture operator / (const GLfloat value, const Texture &rhs){
  Texture tmp;
  tmp.width = rhs.width;
  tmp.height = rhs.height;
  tmp.data = new Color[tmp.width*tmp.height];
  for(int y=0; y < tmp.height; y++)
    for(int x=0; x < tmp.width; x++)
      tmp.data[y*tmp.width+x] = value / rhs.data[y*tmp.width+x];
  return tmp;
}

//////////////////////////////
//    Arithmetic updates    //
//////////////////////////////
#include <cstdio>

Texture& Texture::operator = (const Texture &rhs){
  width = rhs.width;
  height = rhs.height;
  if(NULL != this->data)
    delete[] this->data;
  this->data = new Color[width*height];
  for(int y=0; y < height; y++){
    for(int x=0; x < width; x++){
      this->data[y*width+x] = rhs.data[y*width+x];
    }
  }
  return *this;
}

Texture& Texture::operator = (const Color &value){
  for(int y=0; y < height; y++){
    for(int x=0; x < width; x++){
      data[y*width+x] = value;
    }
  }
  return *this;
}

Texture& Texture::operator = (const GLfloat value){
  for(int y=0; y < height; y++){
    for(int x=0; x < width; x++){
      data[y*width+x] = value;
    }
  }
  return *this;
}

Texture& Texture::operator = (const GLint value){
  for(int y=0; y < height; y++){
    for(int x=0; x < width; x++){
      data[y*width+x] = value;
    }
  } 
  return *this;
}

Texture& Texture::operator += (const Texture &rhs){

  if(width != rhs.width || height != rhs.height)
    return *this;
  for(int y=0; y < height; y++){
    for(int x=0; x < width; x++){
      data[y*width+x] += rhs.data[y*width+x];
    }
  }
  return *this;
}

Texture& Texture::operator += (const Color &value){
  for(int y=0; y < height; y++){
    for(int x=0; x < width; x++){
      data[y*width+x] += value;
    }
  }
  return *this;
}

Texture& Texture::operator += (const GLfloat value){
  for(int y=0; y < height; y++){
    for(int x=0; x < width; x++){
      data[y*width+x] += value;
    }
  }
  return *this;
}

Texture& Texture::operator += (const GLint value){
  for(int y=0; y < height; y++){
    for(int x=0; x < width; x++){
      data[y*width+x] += value;
    }
  }
  return *this;
}

Texture& Texture::operator -= (const Texture &rhs){

  if(width != rhs.width || height != rhs.height)
    return *this;
  for(int y=0; y < height; y++){
    for(int x=0; x < width; x++){
      data[y*width+x] -= rhs.data[y*width+x];
    }
  }
  return *this;
}

Texture& Texture::operator -= (const Color &value){
  for(int y=0; y < height; y++){
    for(int x=0; x < width; x++){
      data[y*width+x] -= value;
    }
  }
  return *this;
}

Texture& Texture::operator -= (const GLfloat value){
  for(int y=0; y < height; y++){
    for(int x=0; x < width; x++){
      data[y*width+x] -= value;
    }
  }
  return *this;
}

Texture& Texture::operator -= (const GLint value){
  for(int y=0; y < height; y++){
    for(int x=0; x < width; x++){
      data[y*width+x] -= value;
    }
  }
  return *this;
}

Texture& Texture::operator *= (const Texture &rhs){

  if(width != rhs.width || height != rhs.height)
    return *this;
  for(int y=0; y < height; y++){
    for(int x=0; x < width; x++){
      data[y*width+x] *= rhs.data[y*width+x];
    }
  }
  return *this;
}

Texture& Texture::operator *= (const Color &value){
  for(int y=0; y < height; y++){
    for(int x=0; x < width; x++){
      data[y*width+x] *= value;
    }
  }
  return *this;
}

Texture& Texture::operator *= (const GLfloat value){
  for(int y=0; y < height; y++){
    for(int x=0; x < width; x++){
      data[y*width+x] *= value;
    }
  }
  return *this;
}

Texture& Texture::operator /= (const Texture &rhs){
  
  if(width != rhs.width || height != rhs.height)
    return *this;
  for(int y=0; y < height; y++){
    for(int x=0; x < width; x++){
      data[y*width+x] /= rhs.data[y*width+x];
    }
  }
  return *this;
}

Texture& Texture::operator /= (const Color &value){
  for(int y=0; y < height; y++){
    for(int x=0; x < width; x++){
      data[y*width+x] /= value;
    }
  }
  return *this;
}

Texture& Texture::operator /= (const GLfloat value){
  for(int y=0; y < height; y++){
    for(int x=0; x < width; x++){
      data[y*width+x] /= value;
    }
  }
  return *this;
}

////////////////////////////////
//      USEFUL FUNCTIONS      //
////////////////////////////////

Texture Texture::Fill(const Color &value) const{
  Texture out(width, height, Color::BLACK);
  for(int y=0; y < out.height; y++){
    for(int x=0; x < out.width; x++){
      out.data[y*width+x] = value;
    }
  }
  return out;
}

Texture Texture::Fill(
    const Color &c0,
    const Color &c1,
    const Color &c2,
    const Color &c3,
    enum Interp flag) const{
  Texture out(width, height, Color::BLACK);
  Color tmp;
  GLfloat alpha;
  GLfloat lin_x;  //   0.0f...x...width
  GLfloat lin_nx; //  width...x...0.0f
  GLfloat lin_y;  //   0.0f...y...height
  GLfloat lin_ny; // height...y...0.0f
  const GLsizei dim = out.width* out.height;

  for(int y=0; y < out.height; y++){
    for(int x=0; x < out.width; x++){
      if(NEAREST == flag){
        lin_x  = NearestInterp(0.0f, (GLfloat)width, (GLfloat)x/width);
        lin_nx = NearestInterp((GLfloat)width, 0.0f, (GLfloat)x/width);
        lin_y  = NearestInterp(0.0f, (GLfloat)height, (GLfloat)y/height);
        lin_ny = NearestInterp((GLfloat)height, 0.0f, (GLfloat)y/height);
      }
      else if(LINEAR == flag){
        lin_x  = LinearInterp(0.0f, (GLfloat)width, (GLfloat)x/width);
        lin_nx = LinearInterp((GLfloat)width, 0.0f, (GLfloat)x/width);
        lin_y  = LinearInterp(0.0f, (GLfloat)height, (GLfloat)y/height);
        lin_ny = LinearInterp((GLfloat)height, 0.0f, (GLfloat)y/height);
      }
      else if(CUBIC == flag){
        lin_x  = CubicInterp(0.0f, (GLfloat)width, (GLfloat)x/width);
        lin_nx = CubicInterp((GLfloat)width, 0.0f, (GLfloat)x/width);
        lin_y  = CubicInterp(0.0f, (GLfloat)height, (GLfloat)y/height);
        lin_ny = CubicInterp((GLfloat)height, 0.0f, (GLfloat)y/height);
      }
      else if(QUINTIC == flag){
        lin_x  = QuinticInterp(0.0f, (GLfloat)width, (GLfloat)x/width);
        lin_nx = QuinticInterp((GLfloat)width, 0.0f, (GLfloat)x/width);
        lin_y  = QuinticInterp(0.0f, (GLfloat)height, (GLfloat)y/height);
        lin_ny = QuinticInterp((GLfloat)height, 0.0f, (GLfloat)y/height);
      }
      else{
        //  Interpolation flag error : do nothing
        return out;
      }
      tmp =  c0 * (lin_nx * lin_ny / (GLfloat)dim);
      alpha = c0.a *  (lin_nx * lin_ny / (GLfloat)dim);
      
      tmp += c1 * (lin_x  * lin_ny / (GLfloat)dim);
      alpha += c1.a *  (lin_x * lin_ny / (GLfloat)dim);
      
      tmp += c2 * (lin_x  * lin_y  / (GLfloat)dim);
      alpha += c2.a *  (lin_x * lin_y / (GLfloat)dim);
      
      tmp += c3 * (lin_nx * lin_y  / (GLfloat)dim); 
      alpha += c3.a *  (lin_nx * lin_y / (GLfloat)dim);
      
      tmp.a = (GLubyte)roundf(alpha);
      out.data[y*out.width+x] = tmp;
    }
  }
  return out;
}

Texture Texture::Radial(const Color &c0, const Color &c1, enum Interp flag) const{
  Texture out(width, height, Color::NONE);
  GLfloat dist;
  const GLfloat x0 = ((GLfloat)out.width -1.0f) / 2.0f; // Center coordinate (X axis)
  const GLfloat y0 = ((GLfloat)out.height-1.0f) / 2.0f; // Center Coordinate (Y axis)
  for(int y=0; y < out.height; y++){
    for(int x=0; x < out.width; x++){
      // distance from the center
      dist = (GLfloat)sqrtf(((GLfloat)x-x0)*((GLfloat)x-x0)+((GLfloat)y-y0)*((GLfloat)y-y0));
      // we are in the circle
      if(dist <= x0 && dist <= y0){
        // get relative distance (0.0f = center | 1.0f = border)
        dist = (GLfloat)dist/(x0 > y0 ? x0 : y0);
        if(NEAREST == flag){
          out.data[y*width+x] = NearestInterp(c0, c1, dist);
          out.data[y*width+x].a = (GLubyte)roundf(NearestInterp((GLfloat)c0.a,(GLfloat)c1.a, dist));
        }
        else if(LINEAR == flag){
          out.data[y*width+x] = LinearInterp(c0, c1, dist);
          out.data[y*width+x].a = (GLubyte)roundf(LinearInterp((GLfloat)c0.a,(GLfloat)c1.a, dist));
        }
        else if(CUBIC == flag){
          out.data[y*width+x] = CubicInterp(c0, c1, dist);
          out.data[y*width+x].a = (GLubyte)roundf(CubicInterp((GLfloat)c0.a,(GLfloat)c1.a, dist));
        }
        else if(QUINTIC == flag){
          out.data[y*width+x] = QuinticInterp(c0, c1, dist);
          out.data[y*width+x].a = (GLubyte)roundf(QuinticInterp((GLfloat)c0.a,(GLfloat)c1.a, dist));
        }
        else // Interpolation Flag error
          return out;
      }
    }
  }
  return out;
}

Texture Texture::Line(
    const Vector &v0,
    const Vector &v1,
    const Color &c0,
    const Color &c1,
    GLfloat w,
    enum Interp flag) const{
  Texture out(width, height, Color::NONE);
  const GLsizei v0_x = (GLsizei)roundf(v0.x);  
  const GLsizei v0_y = (GLsizei)roundf(v0.y);  
  const GLsizei v1_x = (GLsizei)roundf(v1.x);  
  const GLsizei v1_y = (GLsizei)roundf(v1.y);  
  if(roundf(w) < 1.0f){
    return out;
  }
  // v0 and v1 are the same point
  if(v0_x == v1_x && v0_y == v1_y){
    out = out.Circle(v0, 0.0f, w , c0, c0, flag);
    return out;
  }
 
  Vector *start;
  Vector *end;
  int size;
  size = (((int)roundf(w)) % 2 == 0) ? (int)w-1 : (int)w ;
  start = new Vector[size];
  end = new Vector[size];
  const int center = (size-1)/2; 

  if(v0_y == v1_y){ // horizontal line
    for(int loop=0; loop < size; loop++){
      start[loop].x = v0_x;
      start[loop].y = v0_y - (GLfloat)center + (GLfloat)loop;
      end[loop].x = v1_x;
      end[loop].y = v1_y - (GLfloat)center + (GLfloat)loop;
    }
  }
  else if(v0_x == v1_x){ // vertical line
    for(int loop=0; loop < size; loop++){
      start[loop].x = v0_x - (GLfloat)center + (GLfloat)loop;
      start[loop].y = v0_y;
      end[loop].x = v1_x - (GLfloat)center + (GLfloat)loop;
      end[loop].y = v1_y;
    }
  }
  else if((v1_x-v0_x > 0 && v1_y-v0_y > 0) ||
      (v1_x-v0_x < 0 && v1_y-v0_y < 0)){ // First or Third Quarter
    for(int loop=0; loop < size; loop++){
      start[loop].x = v0_x - (GLfloat)center + (GLfloat)loop;
      start[loop].y = v0_y + (GLfloat)center - (GLfloat)loop;
      end[loop].x = v1_x - (GLfloat)center + (GLfloat)loop;
      end[loop].y = v1_y + (GLfloat)center - (GLfloat)loop;
    }
  }
  else if((v1.x-v0.x < 0.0f && v1.y-v0.y > 0.0f) ||
      (v1.x-v0.x > 0.0f && v1.y-v0.y < 0.0f)){ // Second or Fourth Quarter
    for(int loop=0; loop < size; loop++){
      start[loop].x = v0_x - (GLfloat)center + (GLfloat)loop;
      start[loop].y = v0_y - (GLfloat)center + (GLfloat)loop;
      end[loop].x = v1_x - (GLfloat)center + (GLfloat)loop;
      end[loop].y = v1_y - (GLfloat)center + (GLfloat)loop;
    }
  }
  else{ // I miss something ^^
    return out;
  }

  const GLfloat dist = (GLfloat)sqrtf(
      ((GLfloat)v1_x-v0_x)*(v1_x-v0_x)+
      ((GLfloat)v1_y-v0_y)*(v1_y-v0_y));
  GLfloat dist_r;
  const GLsizei step_h = (GLsizei)(end[center].x - start[center].x);
  const GLsizei step_v = (GLsizei)(end[center].y - start[center].y);
  const GLfloat coef =  (GLfloat)fabsf((GLfloat)step_h / (GLfloat)(step_v+1.0f));
  GLsizei nb_h = 0;
  GLsizei nb_v = 0;
  GLfloat tmp_h, tmp_v;
  // filling texture...
  while(start[center].x != v1_x || start[center].y != v1_y){
    dist_r = (GLfloat)sqrtf(
        (start[center].x-v0_x)*(start[center].x-v0_x)+
        (start[center].y-v0_y)*(start[center].y-v0_y));
    dist_r /= dist;
    // fill texture
    for(int i=0; i < size; i++){
      if(((int)start[i].y*width+(int)start[i].x < 0)||
          ((int)start[i].y*width+(int)start[i].x >= width*height))
        continue; // value not in the texture

      if(NEAREST == flag){
        out.data[(int)start[i].y*width+(int)start[i].x] = NearestInterp(c0, c1, dist_r);
        out.data[(int)start[i].y*width+(int)start[i].x].a = (GLubyte)roundf(
            NearestInterp((GLfloat)c0.a, (GLfloat)c1.a, dist_r));
      }
      else if(LINEAR == flag){
        out.data[(int)start[i].y*width+(int)start[i].x] = LinearInterp(c0, c1, dist_r);
        out.data[(int)start[i].y*width+(int)start[i].x].a = (GLubyte)roundf(
            LinearInterp((GLfloat)c0.a, (GLfloat)c1.a, dist_r));
      }
      else if(CUBIC == flag){
        out.data[(int)start[i].y*width+(int)start[i].x] = CubicInterp(c0, c1, dist_r);
        out.data[(int)start[i].y*width+(int)start[i].x].a = (GLubyte)roundf(
            CubicInterp((GLfloat)c0.a, (GLfloat)c1.a, dist_r));
      }
      else if(QUINTIC == flag){
        out.data[(int)start[i].y*width+(int)start[i].x] = QuinticInterp(c0, c1, dist_r);
        out.data[(int)start[i].y*width+(int)start[i].x].a = (GLubyte)roundf(
            QuinticInterp((GLfloat)c0.a, (GLfloat)c1.a, dist_r));
      }
      else{ // Error
        delete[] start;
        delete[] end;
        return out;
      }
    } // end for

    // find next pixel
    tmp_h = ((GLfloat)nb_h+1)/((GLfloat)nb_v+1.0f);
    tmp_v = ((GLfloat)nb_h) / (((GLfloat)nb_v+2.0f));
    if(fabsf(coef - tmp_h) <= fabsf(coef - tmp_v)){
      nb_h++;
      for(int i=0; i < size; i++){
        start[i].x = (end[i].x > start[i].x) ? (start[i].x + 1.0f) : (start[i].x - 1.0f);
      }
    }
    else{
      nb_v++;
      for(int i=0; i < size; i++){
        start[i].y = (end[i].y > start[i].y) ? (start[i].y + 1.0f) : (start[i].y - 1.0f);
      }
    }
  } // end while
  delete[] start;
  delete[] end;
  return out;
}

Texture Texture::Rectangle(  
    const Vector &v0,
    const Vector &v1,
    const Color &c0,
    const Color &c1,
    const Color &c2,
    const Color &c3,
    enum Interp flag) const{
  Texture out(width, height, Color::NONE);
  Color tmp;
  GLfloat alpha;
  GLfloat lin_x;  // v0.x...x...v1.x
  GLfloat lin_nx; // v1.x...x...v0.x
  GLfloat lin_y;  // v0.y...y...v1.y
  GLfloat lin_ny; // v1.y...y...v0.y
  const GLsizei v0_x = (GLsizei)roundf(v0.x);  
  const GLsizei v0_y = (GLsizei)roundf(v0.y);  
  const GLsizei v1_x = (GLsizei)roundf(v1.x);  
  const GLsizei v1_y = (GLsizei)roundf(v1.y);  
  const GLsizei dim = ((v1_x-v0_x)*(v1_y-v0_y));

  if( v0_x < 0          ||
      v0_x > (width-1)  ||
      v0_y < 0          ||
      v0_y > (height-1) ||
      v1_x <= v0_x      ||
      v1_x > (width-1)  ||
      v1_y <= v0_y      ||
      v1_y > (height-1))
    return out;

  for(int y=v0_y; y < v1_y; y++){
    for(int x=v0_x; x < v1_x; x++){
      if(NEAREST == flag){
        lin_x  = NearestInterp(0.0f, (GLfloat)v1_x-v0_x, ((GLfloat)x-v0_x)/(v1_x-v0_x));
        lin_nx = NearestInterp((GLfloat)v1_x-v0_x, 0.0f, ((GLfloat)x-v0_x)/(v1_x-v0_x));
        lin_y  = NearestInterp(0.0f, (GLfloat)v1_y-v0_y, ((GLfloat)y-v0_y)/(v1_y-v0_y));
        lin_ny = NearestInterp((GLfloat)v1_y-v0_y, 0.0f, ((GLfloat)y-v0_y)/(v1_y-v0_y));
      }
      else if(LINEAR == flag){
        lin_x  = LinearInterp(0.0f, (GLfloat)v1_x-v0_x, ((GLfloat)x-v0_x)/(v1_x-v0_x));
        lin_nx = LinearInterp((GLfloat)v1_x-v0_x, 0.0f, ((GLfloat)x-v0_x)/(v1_x-v0_x));
        lin_y  = LinearInterp(0.0f, (GLfloat)v1_y-v0_y, ((GLfloat)y-v0_y)/(v1_y-v0_y));
        lin_ny = LinearInterp((GLfloat)v1_y-v0_y, 0.0f, ((GLfloat)y-v0_y)/(v1_y-v0_y));
      }
      else if(CUBIC == flag){
        lin_x  = CubicInterp(0.0f, (GLfloat)v1_x-v0_x, ((GLfloat)x-v0_x)/(v1_x-v0_x));
        lin_nx = CubicInterp((GLfloat)v1_x-v0_x, 0.0f, ((GLfloat)x-v0_x)/(v1_x-v0_x));
        lin_y  = CubicInterp(0.0f, (GLfloat)v1_y-v0_y, ((GLfloat)y-v0_y)/(v1_y-v0_y));
        lin_ny = CubicInterp((GLfloat)v1_y-v0_y, 0.0f, ((GLfloat)y-v0_y)/(v1_y-v0_y));
      }
      else if(QUINTIC == flag){
        lin_x  = QuinticInterp(0.0f, (GLfloat)v1_x-v0_x, ((GLfloat)x-v0_x)/(v1_x-v0_x));
        lin_nx = QuinticInterp((GLfloat)v1_x-v0_x, 0.0f, ((GLfloat)x-v0_x)/(v1_x-v0_x));
        lin_y  = QuinticInterp(0.0f, (GLfloat)v1_y-v0_y, ((GLfloat)y-v0_y)/(v1_y-v0_y));
        lin_ny = QuinticInterp((GLfloat)v1_y-v0_y, 0.0f, ((GLfloat)y-v0_y)/(v1_y-v0_y));
      }
      else{
        //  Interpolation flag error : do nothing
        return out;
      }
      tmp =  c0 * (lin_nx * lin_ny / (GLfloat)dim);
      alpha = c0.a *  (lin_nx * lin_ny / (GLfloat)dim);
      
      tmp += c1 * (lin_x  * lin_ny / (GLfloat)dim);
      alpha += c1.a *  (lin_x * lin_ny / (GLfloat)dim);
      
      tmp += c2 * (lin_x  * lin_y  / (GLfloat)dim);
      alpha += c2.a *  (lin_x * lin_y / (GLfloat)dim);
      
      tmp += c3 * (lin_nx * lin_y  / (GLfloat)dim); 
      alpha += c3.a *  (lin_nx * lin_y / (GLfloat)dim);
      
      tmp.a = (GLubyte)roundf(alpha);
      out.data[y*out.width+x] = tmp;
    }
  }
  return out;
}

Texture Texture::Circle(
    const Vector &c, // center
    GLfloat r0, //  0 <= r0 <= min(width,height)
    GLfloat r1, // r0 <= r1 <= min(width,height)
    const Color &c0, // r0 color
    const Color &c1, // r1 color
    enum Interp flag) const{
  Texture out(width, height, Color::NONE);
  GLfloat dist;
  for(int y=0; y < out.height; y++){
    for(int x=0; x < out.width; x++){
      // distance from the center
      dist = (GLfloat)sqrtf(((GLfloat)x-c.x)*((GLfloat)x-c.x)+((GLfloat)y-c.y)*((GLfloat)y-c.y));
      // we are in the circle
      if(dist >= r0 && dist <= r1){
        // get relative distance (0.0f = center | 1.0f = border)
        dist = (dist-r0)/(r1-r0);
        if(NEAREST == flag){
          out.data[y*width+x] = NearestInterp(c0, c1, dist);
          out.data[y*width+x].a = (GLubyte)roundf(NearestInterp(c0.a, c1.a, dist));
        }
        else if(LINEAR == flag){
          out.data[y*width+x] = LinearInterp(c0, c1, dist);
          out.data[y*width+x].a = (GLubyte)roundf(LinearInterp(c0.a, c1.a, dist));
        }
        else if(CUBIC == flag){
          out.data[y*width+x] = CubicInterp(c0, c1, dist);
          out.data[y*width+x].a = (GLubyte)roundf(CubicInterp(c0.a, c1.a, dist));
        }
        else if(QUINTIC == flag){
          out.data[y*width+x] = QuinticInterp(c0, c1, dist);
          out.data[y*width+x].a = (GLubyte)roundf(QuinticInterp(c0.a, c1.a, dist));
        }
        else // Interpolation Flag error
          return out;
      }
    }
  }
  return out;
}

Texture Texture::Seamless(void) const{
  Texture out(width, height, Color::BLACK);
  GLfloat a, b, alpha;
  GLfloat blend;

  const GLfloat x0 = (this->width  - 1.0f) / 2.0f;
  const GLfloat y0 = (this->height - 1.0f) / 2.0f;

  for(int y=0; y < height; y++){
    for(int x=0; x < width; x++){
      if((GLfloat)y < y0 && (GLfloat)x < x0){// bottom left
        a = (x0-(GLfloat)x) / x0;
        b = (y0-(GLfloat)y) / y0;
        if (a < 1e-8 && b > 0.99999999)
          blend = 1.0;
        else if (a > 0.99999999 && b < 1e-8)
          blend = 0.0;
        else
          blend = 1.0 - a*b / (a*b+(1.0 - a)*(1.0 - b));

        out.data[y*width+x] = this->data[(y+(int)ceilf(y0))*width + (x+(int)ceilf(x0))]*(1.0f-blend);
        alpha =  this->data[(y+(int)ceilf(y0))*width + (x+(int)ceilf(x0))].a*(1.0f-blend);
        
        out.data[y*width+x] += this->data[y*width+x]*blend;
        alpha += this->data[y*width+x].a*blend;

        out.data[y*width+x].a = (GLubyte)roundf(alpha);
      }
      else if((GLfloat)y < y0 && (GLfloat)x >= x0){// bottom right
        a = (2*x0 -(GLfloat)x) / x0;
        b = (GLfloat)y / y0;
        if (a < 1e-8 && b > 0.99999999)
          blend = 1.0;
        else if (a > 0.99999999 && b < 1e-8)
          blend = 0.0;
        else
          blend = 1.0 - a*b / (a*b+(1.0 - a)*(1.0 - b));

        out.data[y*width+x] = this->data[(y+(int)ceilf(y0))*width + (x-(int)ceilf(x0))]*blend;
        alpha = this->data[(y+(int)ceilf(y0))*width + (x-(int)ceilf(x0))].a*blend;

        out.data[y*width+x] += this->data[y*width+x]*(1.0f-blend);
        alpha += this->data[y*width+x].a*(1.0f-blend);

        out.data[y*width+x].a = (GLubyte)roundf(alpha);
      }
      else if((GLfloat)y >= y0 && (GLfloat)x < x0){// Top left
        a = (x0-(GLfloat)x) / x0;
        b = ((GLfloat)y-y0) / y0;
        if (a < 1e-8 && b > 0.99999999)
          blend = 1.0;
        else if (a > 0.99999999 && b < 1e-8)
          blend = 0.0;
        else
          blend = 1.0 - a*b / (a*b+(1.0 - a)*(1.0 - b));

        out.data[y*width+x] = this->data[(y-(int)ceilf(y0))*width + (x+(int)ceilf(x0))]*(1.0f-blend);
        alpha = this->data[(y-(int)ceilf(y0))*width + (x+(int)ceilf(x0))].a*(1.0f-blend);

        out.data[y*width+x] += this->data[y*width+x]*blend;
        alpha += this->data[y*width+x].a*blend;

        out.data[y*width+x].a = (GLubyte)roundf(alpha);
      }
      else{// Top right
        a = fabsf((GLfloat)x-2*x0) / x0;
        b = fabsf((GLfloat)y-2*y0) / y0;
        if (a < 1e-8 && b > 0.99999999)
          blend = 1.0;
        else if (a > 0.99999999 && b < 1e-8)
          blend = 0.0;
        else
          blend = 1.0 - a*b / (a*b+(1.0 - a)*(1.0 - b));

        out.data[y*width+x] = this->data[(y-(int)ceilf(y0))*width + (x-(int)ceilf(x0))]*blend;
        alpha = this->data[(y-(int)ceilf(y0))*width + (x-(int)ceilf(x0))].a*blend;

        out.data[y*width+x] += this->data[y*width+x]*(1.0f-blend);
        alpha += this->data[y*width+x].a*(1.0f-blend);

        out.data[y*width+x].a = (GLubyte)roundf(alpha);
      }
    } // end for x
  } // end for y

  return out;
}

Texture Texture::Inverse(void) const{
  Texture out(width, height);
  for(int y=0; y < this->height; y++){
    for(int x=0; x < this->width; x++){
      out.data[y*width+x] = this->data[y*width+x].Inverse();
    }
  }
  return out;
}

Texture Texture::Add(const Texture &fg, const Texture &bg){

  if(fg.width != bg.width || fg.height != bg.height){
    return Texture(0, 0);
  }
  Texture out(fg.width, fg.height);
  for(int y=0; y < fg.height; y++){
    for(int x=0; x < fg.width; x++){
      out.data[y*out.width+x] = Color::Add(fg.data[y*fg.width+x], bg.data[y*bg.width+x]);
    }
  }
  return out;
}

Texture Texture::Min(const Texture &t0, const Texture &t1){

  if(t0.width != t1.width || t0.height != t1.height)
    return Texture(0, 0);

  Texture out(t0.width, t0.height);
  for(int y=0; y < t0.height; y++){
    for(int x=0; x < t0.width; x++){
      out.data[y*out.width+x] = Color::Min(t0.data[y*t0.width+x], t1.data[y*t1.width+x]);
    }
  }
  return out;
}

Texture Texture::Max(const Texture &t0, const Texture &t1){

  if(t0.width != t1.width || t0.height != t1.height)
    return Texture(0, 0);

  Texture out(t0.width, t0.height);
  for(int y=0; y < t0.height; y++){
    for(int x=0; x < t0.width; x++){
      out.data[y*out.width+x] = Color::Max(t0.data[y*t0.width+x], t1.data[y*t1.width+x]);
    }
  }
  return out;
}

/*
// out = (in0*in1)/255
static Texture Texture::Multiply(Texture &in0, Texture &in1){
int index;
for(int i=0; i < height; i++){
for(int j=0; j < width; j++){
index = (i*width+j)*4;

out[index+0] = (in0[index+0]*in1[index+0])/255;

out[index+1] = (in0[index+1]*in1[index+1])/255;

out[index+2] = (in0[index+2]*in1[index+2])/255;

out[index+3] = in1[index+3];
}
}
}

// out = (in1*256)/(256-in0)
static Texture Texture::Dodge(Texture &in0, Texture &in1){
int index;
GLfloat tmp;
for(int i=0; i < height; i++){
for(int j=0; j < width; j++){
index = (i*width+j)*4;

tmp = (in1[index+0]*256)/(256-in0[index+0]);
out[index+0] = tmp < 0xff ? (GLubyte)tmp : 0xff;

tmp = (in1[index+1]*256)/(256-in0[index+1]);
out[index+1] = tmp < 0xff ? (GLubyte)tmp : 0xff;

tmp = (in1[index+2]*256)/(256-in0[index+2]);
out[index+2] = tmp < 0xff ? (GLubyte)tmp : 0xff;

out[index+3] = in1[index+3]; 
}
}
}

// out = 255 - ((255-in1)*256)/(in0+1)
static Texture Texture::Burn(Texture &in0, Texture &in1){
int index;
GLfloat tmp;
for(int i=0; i < height; i++){
for(int j=0; j < width; j++){
index = (i*width+j)*4;

tmp = 255 - ((GLfloat)(255-in1[index+0])*256)/(in0[index+0]+1);
out[index+0] = tmp < 0xff ? (GLubyte)tmp : 0xff;

tmp = 255 - ((GLfloat)(255-in1[index+1])*256)/(in0[index+1]+1);
out[index+1] = tmp < 0xff ? (GLubyte)tmp : 0xff;

tmp = 255 - ((GLfloat)(255-in1[index+2])*256)/(in0[index+2]+1);
out[index+2] = tmp < 0xff ? (GLubyte)tmp : 0xff;

out[index+3] = in1[index+3];
}
}
}

// M > 128  : E = 255 - ((255-I)*(255-(2*(M-128))))/256
// M <= 128 : E = (I*M)/128
static Texture Texture::HardLight(Texture &in0, Texture &in1){
  int index;
  GLfloat tmp;
  for(int i=0; i < height; i++){
    for(int j=0; j < width; j++){
      index = (i*width+j)*4;

      if(in0[index+0] > 128)
        tmp = 255 - ((GLfloat)((255-in1[index+0])*(255-(2*(in0[index+0]-128)))))/256;
      else
        tmp = (in1[index+0]*in0[index+0])/128;
      out[index+0] = tmp < 0xff ? (GLubyte)tmp : 0xff;

      if(in0[index+1] > 128)
        tmp = 255 - ((GLfloat)((255-in1[index+1])*(255-(2*(in0[index+1]-128)))))/256;
      else
        tmp = (in1[index+1]*in0[index+1])/128;
      out[index+1] = tmp < 0xff ? (GLubyte)tmp : 0xff;

      if(in0[index+2] > 128)
        tmp = 255 - ((GLfloat)((255-in1[index+2])*(255-(2*(in0[index+2]-128)))))/256;
      else
        tmp = (in1[index+2]*in0[index+2])/128;
      out[index+2] = tmp < 0xff ? (GLubyte)tmp : 0xff;

      out[index+3] = in1[index+3];
    }
  }
}

// Rs = (255 - ((255-I)*(255-M)))/256
// E = (((255-I)*M*I)+(I*Rs))/255
static Texture Texture::SoftLight(Texture &in0, Texture &in1){
  int index;
  GLfloat rs;
  GLfloat tmp;
  for(int i=0; i < height; i++){
    for(int j=0; j < width; j++){
      index = (i*width+j)*4;

      rs  = ((GLfloat)(255 - ((255-in1[index+0])*(255-in0[index+0]))))/256;
      tmp = (((255-in1[index+0])*in0[index+0]*in1[index+0])+(in1[index+0]*rs))/255;
      out[index+0] = tmp < 0xff ? (GLubyte)tmp : 0xff;

      rs  = ((GLfloat)(255 - ((255-in1[index+1])*(255-in0[index+1]))))/256;
      tmp = (((255-in1[index+1])*in0[index+1]*in1[index+1])+(in1[index+1]*rs))/255;
      out[index+1] = tmp < 0xff ? (GLubyte)tmp : 0xff;

      rs  = ((GLfloat)(255 - ((255-in1[index+2])*(255-in0[index+2]))))/256;
      tmp = (((255-in1[index+2])*in0[index+2]*in1[index+2])+(in1[index+2]*rs))/255;
      out[index+2] = tmp < 0xff ? (GLubyte)tmp : 0xff;

      out[index+3] = in1[index+3];
    }
  }
}

// E = |I-M|
static Texture Texture::Difference(Texture &in0, Texture &in1){
  int index;
  GLfloat tmp;
  for(int i=0; i < height; i++){
    for(int j=0; j < width; j++){
      index = (i*width+j)*4;

      tmp = in1[index+0]-in0[index+0];
      out[index+0] = tmp > 0x00 ? (GLubyte)tmp : (GLubyte)-tmp;

      tmp = in1[index+1]-in0[index+1];
      out[index+1] = tmp > 0x00 ? (GLubyte)tmp : (GLubyte)-tmp;

      tmp = in1[index+2]-in0[index+2];
      out[index+2] = tmp > 0x00 ? (GLubyte)tmp : (GLubyte)-tmp;

      out[index+3] = in1[index+3];
    }
  }
}
*/
/*
   void Texture::Cloud(
   GLubyte *out,
   GLsizei width,
   GLsizei height,
   int octaves,
   double persistence,
   double frequency,
   int seed,
   GLfloat coefX,
   GLfloat coefY,
   GLubyte flag){
   Perlin perlin(octaves,persistence,frequency,seed);
   int index;
   GLfloat vX,vY;
   for(int i=0; i < height; i++){
   for(int j=0; j < width; j++){
   index = (i*width+j)*4;
   vX = coefX*j/(width-1.0f);
   vY = coefY*i/(height-1.0f);

   out[index+0] = flag & 0x01 ? perlin.Perlini(vX,vY) : 0x00;

   out[index+1] = flag & 0x02 ? perlin.Perlini(vX,vY) : 0x00;

   out[index+2] = flag & 0x04 ? perlin.Perlini(vX,vY) : 0x00;

   out[index+3] = flag & 0x08 ? perlin.Perlini(vX,vY) : 0x00;
   }
   }
   }
*/
